package com.leyou.search.service.impl;

import com.fasterxml.jackson.core.type.TypeReference;
import com.leyou.common.enums.LyExceptionEnum;
import com.leyou.common.exception.LyException;
import com.leyou.common.utils.BeanHelper;
import com.leyou.common.utils.JsonUtils;
import com.leyou.common.vo.PageResult;
import com.leyou.item.client.ItemClient;
import com.leyou.item.dto.*;
import com.leyou.search.dto.GoodsDTO;
import com.leyou.search.dto.SearchRequest;
import com.leyou.search.pojo.Goods;
import com.leyou.search.respository.GoodsRespository;
import com.leyou.search.service.SearchService;
import org.apache.commons.lang.StringUtils;
import org.elasticsearch.index.query.*;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
import org.springframework.data.elasticsearch.core.aggregation.AggregatedPage;
import org.springframework.data.elasticsearch.core.query.FetchSourceFilter;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.*;
import java.util.stream.Collectors;

@Service
@Transactional
public class SearchServiceImpl implements SearchService {

    @Autowired
    private ItemClient itemClient;

    @Autowired
    private GoodsRespository goodsRespository;

    @Autowired
    private ElasticsearchTemplate esTemplate;

    @Override
    public Goods buildGoods(SpuDTO spuDTO) {
        Long spuId = spuDTO.getId();

        // 1 商品相关搜索信息的拼接：名称、分类、品牌等
        List<CategoryDTO> categoryDTOList = itemClient.queryByCategoryIds(spuDTO.getCategoryIds());
        String categoryName = categoryDTOList.stream().map(CategoryDTO::getName).collect(Collectors.joining("，"));
        BrandDTO brandDTO = itemClient.queryById(spuDTO.getBrandId());
        String all = spuDTO.getName()+categoryName+brandDTO.getName();
        // 2 spu下的所有sku的JSON数组
        List<SkuDTO> skuDTOList = itemClient.querySkuBySpuId(spuId);
        List<Map<String, Object>> skuList = skuDTOList.stream().map(skuDTO -> {
            Map<String, Object> map = new HashMap<>();
            map.put("id", skuDTO.getId());
            map.put("image", StringUtils.substringBefore(skuDTO.getImages(),","));
            map.put("price", skuDTO.getPrice());
            map.put("title", skuDTO.getTitle());
            return map;
        }).collect(Collectors.toList());
        String skus = JsonUtils.toString(skuList);
        //3 组装规格参数
        Map<String,Object> specMap = new HashMap<>();
        //3.1获取规格名称
        List<SpecParamDTO> paramDTOList = itemClient.querySpecParam(null, spuDTO.getCid3(), true);
        //3.2获取规格参数值
        SpuDetailDTO spuDetailDTO = itemClient.querySpuDetailBySpuId(spuId);
        //获取通用规格  {"1":"海尔","2":"老年机","3":"2015","5":"100","6":"塑料","7":"鸿蒙","8":"海思"}
        Map<Long, Object> genericSpec = JsonUtils.toMap(spuDetailDTO.getGenericSpec(), Long.class, Object.class);
        //获取特有规格  {"4":["亮黑色","宝石蓝"],"12":["8G"],"13":["64G","128G"]}    Map<Long,List<String>>
        //数据结构比较复杂是，可以使用JsonUtils 转为nativeRead
        Map<Long, List<String>> specialSpec = JsonUtils.nativeRead(spuDetailDTO.getSpecialSpec(), new TypeReference<Map<Long, List<String>>>() {
        });


        for (SpecParamDTO specParamDTO : paramDTOList) {
            //获取规参数名
            String specName = specParamDTO.getName();
            //获取规格参数值
            Object value=null;
            //判断是否是通用规格参数
            if (specParamDTO.getGeneric()) {
                //通用规格参数
                value=genericSpec.get(specParamDTO.getId());
            }else{
                //特有规格参数
                value=specialSpec.get(specParamDTO.getId());
            }

            //判断规格参数是否是数值类型  如果是数值类型，规格参数值是该数值类型所在的区间
            // 例如：当前这款手机屏幕尺寸是5.3  1.5以下 5.0-5.5 6.0以上  其它
            if(specParamDTO.getIsNumeric()){
                // 是数字类型，分段
                value = chooseSegment(value, specParamDTO);
            }

            specMap.put(specName,value);
        }

        //获取sku中的价格，并去重 set
        Set<Long> priceSet = skuDTOList.stream().map(SkuDTO::getPrice).collect(Collectors.toSet());

        //组装goods
        Goods goods = new Goods();
        goods.setSubTitle(spuDTO.getSubTitle());
        goods.setSpecs(specMap);
        goods.setSkus(skus);
        goods.setPrice(priceSet);
        goods.setId(spuId);
        goods.setCreateTime(spuDTO.getCreateTime().getTime());
        goods.setCategoryId(spuDTO.getCid3());
        goods.setAll(all);
        goods.setBrandId(spuDTO.getBrandId());

        return goods;
    }



    private String chooseSegment(Object value, SpecParamDTO p) {
        if (value == null || StringUtils.isBlank(value.toString())) {
            return "其它";
        }
        double val = parseDouble(value.toString());
        String result = "其它";
        // 保存数值段
        for (String segment : p.getSegments().split(",")) {
            String[] segs = segment.split("-");
            // 获取数值范围
            double begin = parseDouble(segs[0]);
            double end = Double.MAX_VALUE;
            if (segs.length == 2) {
                end = parseDouble(segs[1]);
            }
            // 判断是否在范围内
            if (val >= begin && val < end) {
                if (segs.length == 1) {
                    result = segs[0] + p.getUnit() + "以上";
                } else if (begin == 0) {
                    result = segs[1] + p.getUnit() + "以下";
                } else {
                    result = segment + p.getUnit();
                }
                break;
            }
        }
        return result;
    }

    private double parseDouble(String str) {
        try {
            return Double.parseDouble(str);
        } catch (Exception e) {
            return 0;
        }
    }

    @Override
    public PageResult<GoodsDTO> searchGoods(SearchRequest request) {
        //0.关键字健壮性校验
        String key = request.getKey();
        if(StringUtils.isBlank(key)){
            throw new LyException(LyExceptionEnum.INVALID_PARAM_ERROR);
        }

        //2.构建查询构建对象
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();

        QueryBuilder basicQuery= basicQuery(request);
        //2.1.组装查询条件
        queryBuilder.withQuery(basicQuery);
        //过滤页面显示字段
        queryBuilder.withSourceFilter(new FetchSourceFilter(new String[]{"id","subTitle","skus"},null));

        //2.2 指定分页查询
        Integer page = request.getPage()-1;
        Integer size = request.getSize();
        queryBuilder.withPageable(PageRequest.of(page,size));

        //3.执行查询操作
        AggregatedPage<Goods> result = esTemplate.queryForPage(queryBuilder.build(), Goods.class);

        //4.处理查询结果
        long total = result.getTotalElements();
        int totalPages = result.getTotalPages();

        if (page>totalPages) {
            page=1;
        }

        List<Goods> goodsList = result.getContent();
        return new PageResult<GoodsDTO>(total,totalPages,BeanHelper.copyWithCollection(goodsList,GoodsDTO.class));
    }

    /**
     * 构建基础查询
     * @param request
     * @return
     */
    private QueryBuilder basicQuery(SearchRequest request) {
        //1、构建布尔查询
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
        //主查询条件
        boolQueryBuilder.must(QueryBuilders.matchQuery("all", request.getKey()).operator(Operator.AND));
        //构建过滤条件
        Map<String, Object> filters = request.getFilters();
        for (String key : filters.keySet()) {
            Object value = filters.get(key);
            if ("品牌".equals(key)) {
                key="brandId";
            } else if ("分类".equals(key)) {
                key="categoryId";
            }else{
                key="specs."+key;
            }
            boolQueryBuilder.filter(QueryBuilders.termQuery(key,value));
        }

        return boolQueryBuilder;
    }

    @Override
    public Map<String, List<?>> searchFilters(SearchRequest request) {
        //查询结果要有序
        Map<String, List<?>> filterMap = new LinkedHashMap<>();

        //1.构建查询构建对象
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();

        QueryBuilder basicQuery= basicQuery(request);
        //1.1.组装查询条件
        queryBuilder.withQuery(basicQuery);
        //过滤页面显示字段
        queryBuilder.withSourceFilter(new FetchSourceFilter(null,null));

        //1.2 指定分页查询
        queryBuilder.withPageable(PageRequest.of(0,1));

        //2.构建聚合条件
        //2.1添加品牌聚合条件
        String brandAgg="brandAgg";
        queryBuilder.addAggregation(AggregationBuilders.terms(brandAgg).field("brandId"));

        //2.2分类聚合
        String categoryAgg="categoryAgg";
        queryBuilder.addAggregation(AggregationBuilders.terms(categoryAgg).field("categoryId"));
        
        //3.执行查询
        AggregatedPage<Goods> result = esTemplate.queryForPage(queryBuilder.build(), Goods.class);

        //4、解析聚合结果
        Aggregations aggregations = result.getAggregations();
        //解析品牌聚合结果
        Terms brandTerms = aggregations.get(brandAgg);
        handleBrandAgg(brandTerms,filterMap);
        //解析分类结果
        Terms categoryTerms =  aggregations.get(categoryAgg);
        List<CategoryDTO> categoryDTOList = handleCategoryAgg(categoryTerms, filterMap);
        //当分类聚合结果只有一个元素时，再基于该分类查询规格数据，做聚合操作
        if(categoryDTOList!=null && categoryDTOList.size()==1){
            handleSpecsAgg(request,categoryDTOList,filterMap);
        }

        return filterMap;
    }

    @Override
    public void saveGoodsToIndex(Long id) {
        SpuDTO spuDTO = itemClient.querySpuBySpuId(id);
        Goods goods = buildGoods(spuDTO);
        goodsRespository.save(goods);
    }

    @Override
    public void delteGoodsFromIndex(Long id) {
        goodsRespository.deleteById(id);
    }

    /**
     * 聚合规格参数选项
     * @param categoryDTOList
     * @param filterMap
     */
    private void handleSpecsAgg(SearchRequest request,List<CategoryDTO> categoryDTOList, Map<String, List<?>> filterMap) {

        //1.构建查询构建对象
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();

        QueryBuilder basicQuery= basicQuery(request);
        //1.1.组装查询条件
        queryBuilder.withQuery(basicQuery);
        //过滤页面显示字段
        queryBuilder.withSourceFilter(new FetchSourceFilter(null,null));

        //1.2 指定分页查询
        queryBuilder.withPageable(PageRequest.of(0,1));

        //2 规格聚合
        Long cid = categoryDTOList.get(0).getId();//分类id
        //查询该分类下用于搜索的规格参数名
        List<SpecParamDTO> specParamDTOList = itemClient.querySpecParam(null, cid, true);
        //规格选项聚合
        for (SpecParamDTO specParamDTO : specParamDTOList) {
            //规格参数名
            String name = specParamDTO.getName();
            //指定聚合条件
            queryBuilder.addAggregation(AggregationBuilders.terms(name).field("specs."+name));
        }

        //3、执行查询
        AggregatedPage<Goods> result = esTemplate.queryForPage(queryBuilder.build(), Goods.class);

        //4、解析聚合结果
        Aggregations aggregations = result.getAggregations();
        for (SpecParamDTO specParamDTO : specParamDTOList) {
            String name = specParamDTO.getName();
            Terms specTerm = aggregations.get(name);//获取聚合结果
            List<String> values = specTerm.getBuckets().stream()
                    .map(Terms.Bucket::getKeyAsString)
                    .filter(StringUtils::isNotBlank)
                    .collect(Collectors.toList());
            filterMap.put(name,values);

        }
    }

    /**
     * 解析分类聚合结果
     * @param categoryTerms
     * @param filterMap
     */
    private List<CategoryDTO> handleCategoryAgg(Terms categoryTerms, Map<String, List<?>> filterMap) {
        //收集分类聚合结果集 （分类id集合）
        List<Long> categoryIds = categoryTerms.getBuckets().stream()
                .map(Terms.Bucket::getKeyAsNumber)
                .map(Number::longValue).collect(Collectors.toList());

        //调用接口查询分类数据
        List<CategoryDTO> categoryDTOList = itemClient.queryByCategoryIds(categoryIds);

        filterMap.put("分类",categoryDTOList);
        return categoryDTOList;
    }

    /**
     * 解析品牌聚合结果
     * @param brandTerms
     * @param filterMap
     */
    private void handleBrandAgg(Terms brandTerms, Map<String, List<?>> filterMap) {
        //收集品牌聚合结果集 （品牌id集合）
        List<Long> brandIds = brandTerms.getBuckets().stream()
                .map(Terms.Bucket::getKeyAsNumber)
                .map(Number::longValue).collect(Collectors.toList());

        //调用接口查询品牌数据
        List<BrandDTO> brandDTOList = itemClient.queryBrandByIds(brandIds);

        filterMap.put("品牌",brandDTOList);

    }
}
